package cn.tedu.jdbc;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;

/*
* 1、PreparedStatement 填充占位符时，下标都是从 1 开始，通用的情况往往不知道填充的参数是何种类型，通过 setObject(..) 即可

2、与增删改不同的是，执行查询时，我们需要使用 executeQuery() 方法，并接收返回的 ResultSet 对象

3、ResultSet 并没有直接存储返回列的数量，我们需要获取到 ResultSetMetaData 获取列的数量，以及列的别名

4、rs.next() 实例方法用于判断结果集中是否存在下一条数据，它的作用相当于集合迭代器中的 hasNext() ，同时带有指针下移的操作

5、在使用 clazz.newInstance() 方法获取对象时，一定要记得实体类必须要有空参构造器

6、使用反射方法封装对象是唯一通用的方法，这样才能保证不论我们封装的是何种类型，都可以在运行时获取到对应的属性信息
*/
public class TestJDBC1 {
    /**
     * 本方法用于获取数据库连接
     *
     * @return
     */
    private Connection getConnection() {
        String driver = "com.mysql.cj.jdbc.Driver";//数据库驱动类所对应的字符串  可以不写
        String URL = "jdbc:mysql://localhost:3306/cgb2105?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8";//格式为 数据库协议://端口号或者ip地址/数据库名称？时区&编码            注意：  ？时区&编码可以不写
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(URL, "root", "root");
        } catch (SQLException throwables) {
            System.out.println("数据库连接异常");
            throwables.printStackTrace();
        }
        return connection;
    }

    /**
     * 本方法用于通用的查询操作
     *
     * @param c
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> ArrayList<T> select(Class<T> c, String sql, Object... args) {
        ArrayList<T> ts = new ArrayList<>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {


            connection = getConnection();//获取数据库连接
            preparedStatement = connection.prepareStatement(sql);//创建一个PreparedStatement对象，用于将参数化的SQL语句发送到数据库。
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);//填充占位符
            }
            resultSet = preparedStatement.executeQuery();//执行此 PreparedStatement对象中的SQL查询，并返回查询 PreparedStatement的 ResultSet对象。
            ResultSetMetaData metaData = preparedStatement.getMetaData();//检索此 ResultSet对象的列的数量，类型和属性。
            int columnCount = metaData.getColumnCount();//返回此 ResultSet对象中的列数。
            while (resultSet.next()) {
                T t = c.newInstance(); //实例化数据库表的实体类
                for (int i = 0; i < columnCount; i++) {     //循环给列赋值
                    String columnLabel1 = metaData.getColumnLabel(i + 1);//获取指定列的建议标题用于打印输出和显示。 建议的标题通常由SQL AS子句AS 。 如果SQL AS没有指定，从返回的值getColumnLabel将是相同的由返回的值getColumnName方法。
                    Object object = resultSet.getObject(i + 1);//获取此的当前行中指定列的值ResultSet作为对象Object在Java编程语言。此方法将以Java对象的形式返回给定列的值。
                    Field declaredField = c.getDeclaredField(columnLabel1);//通过反射句获取私有属性
                    declaredField.setAccessible(true);//设置私有属性可见
                    declaredField.set(t, object);//给属性赋值
                    ts.add(t);
                }
                return ts;

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(resultSet, preparedStatement, connection);
        }
        //    System.out.println(ts);
        return ts;
    }

    /**
     * 本方法用于数据库的修改操作
     *
     * @param sql
     * @param obj
     */
    public void change(String sql, Object... obj) {
        Connection connection = getConnection();
        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        for (int i = 0; i < obj.length; i++) {
            try {
                preparedStatement.setObject(i + 1, obj[i]);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        try {
            preparedStatement.executeLargeUpdate();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            close(preparedStatement, connection);//关闭资源
        }

    }

    /**
     * 通用的资源关闭操作
     *
     * @param obj
     */
    public void close(Object... obj) {
        try {


            for (int i = 0; i < obj.length; i++) {
                if (obj[i] instanceof ResultSet && obj[i] != null) {
                    ((ResultSet) obj[i]).close();
                    System.out.println("ResultSet资源已关闭");
                }
                if (obj[i] instanceof PreparedStatement && obj[i] != null) {
                    ((PreparedStatement) obj[i]).close();
                    System.out.println("PreparedStatement已关闭");

                }
                if (obj[i] instanceof Connection && obj[i] != null) {
                    ((Connection) obj[i]).close();
                    System.out.println("Connection已关闭");
                }
            }
        } catch (Exception e) {
            System.out.println("资源关闭异常");
        }
    }

    private void close1(PreparedStatement preparedStatement, ResultSet resultSet, Connection connection) {
        try {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
